lib: catch unwinds in RepoCheckoutFilter
authorFelix Krull <f_krull@gmx.de>
Thu, 13 Jun 2019 17:31:37 +0000 (19:31 +0200)
committerColin Walters <walters@verbum.org>
Fri, 6 May 2022 16:53:54 +0000 (12:53 -0400)
rust-bindings/rust/src/repo_checkout_at_options.rs
rust-bindings/rust/src/repo_checkout_at_options/repo_checkout_filter.rs

index 4b49e2c63786d618667d4cb1a82536ccc0929917..a1cd37d9f6def0e97e673af69d0459ad39afc500 100644 (file)
@@ -91,7 +91,7 @@ impl<'a> ToGlibPtr<'a, *const OstreeRepoCheckoutAtOptions> for RepoCheckoutAtOpt
 
         if let Some(filter) = &self.filter {
             options.filter_user_data = filter.to_glib_none().0;
-            options.filter = Some(repo_checkout_filter::filter_trampoline);
+            options.filter = Some(repo_checkout_filter::filter_trampoline_unwindsafe);
         }
 
         Stash(
@@ -187,7 +187,7 @@ mod tests {
             );
             assert_eq!((*ptr).unused_ints, [0; 6]);
             assert_eq!((*ptr).unused_ptrs, [ptr::null_mut(); 3]);
-            assert!((*ptr).filter == Some(repo_checkout_filter::filter_trampoline));
+            assert!((*ptr).filter == Some(repo_checkout_filter::filter_trampoline_unwindsafe));
             assert_eq!(
                 (*ptr).filter_user_data,
                 options.filter.as_ref().unwrap().to_glib_none().0,
index 44a3a7f36607b9ffc4b301f23776c9a11040bf56..cb8190e43fdf5555044247b39f1b0d63d5cd5227 100644 (file)
@@ -3,7 +3,10 @@ use glib::translate::*;
 use glib_sys::gpointer;
 use libc::c_char;
 use ostree_sys::*;
+use std::any::Any;
+use std::panic::catch_unwind;
 use std::path::{Path, PathBuf};
+use std::process::abort;
 
 /// A filter callback to decide which files to checkout from a [Repo](struct.Repo.html). The
 /// function is called for every directory and file in the dirtree.
@@ -62,13 +65,12 @@ impl FromGlibPtrNone<gpointer> for &RepoCheckoutFilter {
 ///
 /// # Panics
 /// If any parameter is a null pointer, the function panics.
-pub(super) unsafe extern "C" fn filter_trampoline(
+unsafe extern "C" fn filter_trampoline(
     repo: *mut OstreeRepo,
     path: *const c_char,
     stat: *mut libc::stat,
     user_data: gpointer,
 ) -> OstreeRepoCheckoutFilterResult {
-    // TODO: handle unwinding
     // We can't guarantee it's a valid pointer, but we can make sure it's not null.
     assert!(!stat.is_null());
     let stat = &*stat;
@@ -85,6 +87,36 @@ pub(super) unsafe extern "C" fn filter_trampoline(
     result.to_glib()
 }
 
+pub(super) unsafe extern "C" fn filter_trampoline_unwindsafe(
+    repo: *mut OstreeRepo,
+    path: *const c_char,
+    stat: *mut libc::stat,
+    user_data: gpointer,
+) -> OstreeRepoCheckoutFilterResult {
+    // Unwinding across an FFI boundary is Undefined Behavior and we have no other way to communicate
+    // the error. We abort() safely to avoid further problems.
+    let result = catch_unwind(move || filter_trampoline(repo, path, stat, user_data));
+    result.unwrap_or_else(|panic| {
+        print_panic(panic);
+        abort()
+    })
+}
+
+fn print_panic(panic: Box<dyn Any>) {
+    eprintln!("A Rust callback invoked by C code panicked.");
+    eprintln!("Unwinding across FFI boundaries is Undefined Behavior so abort() will be called.");
+    let msg = {
+        if let Some(s) = panic.as_ref().downcast_ref::<&str>() {
+            s
+        } else if let Some(s) = panic.as_ref().downcast_ref::<String>() {
+            s
+        } else {
+            "UNABLE TO SHOW VALUE OF PANIC"
+        }
+    };
+    eprintln!("Panic value: {}", msg);
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;